﻿using System.Collections.Generic;
using System.Linq;

namespace Borg.Framework
{
    public static class ExtensionInfoEx
    {
        private static void AddDirectPath(ExtensionInfo fromExtension,
            SlotInfo toSlot, ICollection<Path> paths)
        {
            foreach (var pi in fromExtension.PlugInfos)
            {
                foreach (var si in pi.PluggedInSlots.Where(si => si == toSlot))
                {
                    paths.Add(new Path(new Path.Connection(pi, si)));
                    break;
                }
            }
        }

        private static void AddIndirectPaths(ExtensionInfo fromExtension,
            SlotInfo toSlot, HashSet<ExtensionInfo> visited, List<Path> paths)
        {
            paths.AddRange(from pi in fromExtension.PlugInfos
                           from si in pi.PluggedInSlots
                           let ei = si.ExtensionInfo
                           from path in FindHostPaths(ei, toSlot, visited)
                           select new Path(new Path.Connection(pi, si), path));
        }

        public static ExtensionInfo CreateExtension(this ExtensionTypeInfo extType, bool unique)
        {
            return unique
                ? extType.CreateUniqueExtension()
                : extType.GetSharedExtension(true);
        }

        public static PlugInfo CreateExtension(this PlugTypeInfo plugType, SlotInfo slot)
        {
            return plugType.ExtensionTypeInfo.CreateExtension(slot.Unique).PlugInfos[plugType.Name];
        }

        public static List<Path> FindHostPaths(this ExtensionInfo fromExtension, SlotInfo toSlot)
        {
            var visited = new HashSet<ExtensionInfo>();
            return FindHostPaths(fromExtension, toSlot, visited);
        }

        private static List<Path> FindHostPaths(ExtensionInfo fromExtension,
            SlotInfo toSlot, HashSet<ExtensionInfo> visited)
        {
            if (visited.Contains(fromExtension))
                return null;

            visited.Add(fromExtension);
            var paths = new List<Path>();

            AddDirectPath(fromExtension, toSlot, paths);
            AddIndirectPaths(fromExtension, toSlot, visited, paths);

            visited.Remove(fromExtension);

            return paths;
        }

        public static object FindIncomingObjectTransitive(this ExtensionInfo ext, string slotName)
        {
            return FindIncomingPlugTransitive(ext, slotName).Object;
        }

        public static PlugInfo FindIncomingPlugTransitive(ExtensionInfo ext, string slotName)
        {
            var si = ext.FindProvidedSlotTransitive(slotName);

            return si == null
                ? null
                : si.PluggedPlugInfos[0].ExtensionInfo.PlugInfos[slotName];
        }

        public static SlotInfo FindProvidedSlotTransitive(this ExtensionInfo ext, string slotName)
        {
            if (ext.SlotInfos.Contains(slotName))
                return ext.SlotInfos[slotName];

            return (from si in ext.SlotInfos
                    from pi in si.PluggedPlugInfos
                    select pi.ExtensionInfo.
                        FindProvidedSlotTransitive(slotName)).
                FirstOrDefault(slot => slot != null);
        }
    }
}

